"""Kconfig 语法解析器
增加了 logging 输出以便在大文件解析时跟踪进度。"""
import logging
import re
from typing import List, Dict, Any, Optional, Tuple
from .config_model import (
    ConfigItem,
    MenuItem,
    CommentItem,
    ChoiceItem,
    ConfigProperty,
    Condition,
    DefaultValue,
    Selection,
    RangeRestriction,
)
from .expression_evaluator import ExpressionEvaluator


logger = logging.getLogger(__name__)


class KconfigParser:
    """Kconfig解析器"""

    def __init__(self):
        self.expression_evaluator = ExpressionEvaluator()
        self.current_id = 0
        self.config_items: Dict[str, ConfigItem] = {}
        self.menu_items: Dict[str, MenuItem] = {}
        self.comment_items: Dict[str, CommentItem] = {}
        self.choice_items: Dict[str, ChoiceItem] = {}
        # 未识别语句统计
        self._unknown_lines_count = 0
        # 解析正则表达式
        self.regex_patterns = {
            "menu": re.compile(r'^\s*menu\s+"([^"]+)"', re.IGNORECASE),
            "endmenu": re.compile(r"^\s*endmenu", re.IGNORECASE),
            "config": re.compile(r"^\s*config\s+(\w+)", re.IGNORECASE),
            "menuconfig": re.compile(r"^\s*menuconfig\s+(\w+)", re.IGNORECASE),
            # 支持 choice "Prompt"
            "choice": re.compile(r'^\s*choice(?:\s+"([^"]+)")?', re.IGNORECASE),
            "endchoice": re.compile(r"^\s*endchoice", re.IGNORECASE),
            "comment": re.compile(r'^\s*comment\s+"([^"]+)"', re.IGNORECASE),
            "type": re.compile(
                r"^\s*(bool|tristate|string|int|hex)\s*(.*)", re.IGNORECASE
            ),
            "prompt": re.compile(r'^\s*prompt\s+"([^"]+)"\s*(if\s+.*)?', re.IGNORECASE),
            "depends_on": re.compile(r"^\s*depends\s+on\s+(.*)", re.IGNORECASE),
            "visible_if": re.compile(r"^\s*visible\s+if\s+(.*)", re.IGNORECASE),
            "default": re.compile(
                r'^\s*default\s+(\w+|".*?")\s*(if\s+.*)?', re.IGNORECASE
            ),
            "help": re.compile(r"^\s*help\s*$", re.IGNORECASE),
            "select": re.compile(r"^\s*select\s+(\w+)\s*(if\s+.*)?", re.IGNORECASE),
            "imply": re.compile(r"^\s*imply\s+(\w+)\s*(if\s+.*)?", re.IGNORECASE),
            "range": re.compile(
                r"^\s*range\s+(\d+)\s+(\d+)\s*(if\s+.*)?", re.IGNORECASE
            ),
            # include 指令族
            "source": re.compile(r'^\s*source\s+"([^"]+)"', re.IGNORECASE),
            "rsource": re.compile(r'^\s*rsource\s+"([^"]+)"', re.IGNORECASE),
            "osource": re.compile(r'^\s*osource\s+"([^"]+)"', re.IGNORECASE),
            "orsource": re.compile(r'^\s*orsource\s+"([^"]+)"', re.IGNORECASE),
            # 其他顶层指令
            "mainmenu": re.compile(r'^\s*mainmenu\s+"([^"]+)"', re.IGNORECASE),
            "option_env": re.compile(r'^\s*option\s+env\s*=\s*"?([^"\n]+)"?\s*$', re.IGNORECASE),
            # if/endif 代码块（用于包裹一组项的可见性条件）
            "if": re.compile(r"^\s*if\s+(.*)", re.IGNORECASE),
            "endif": re.compile(r"^\s*endif\b", re.IGNORECASE),
        }

    def generate_id(self) -> str:
        """生成唯一ID"""
        self.current_id += 1
        return f"item-{self.current_id:06d}"

    def parse_condition(self, condition_str: Optional[str]) -> Optional[Condition]:
        """解析条件表达式"""
        if not condition_str or not condition_str.strip():
            return None

        if condition_str.startswith("if"):
            condition_str = condition_str[2:].strip()

        return Condition(expression=condition_str)

    def parse_default_value(self, value_str: str) -> Any:
        """解析默认值"""
        if not value_str:
            return None

        # 移除引号
        if value_str.startswith('"') and value_str.endswith('"'):
            return value_str[1:-1]

        # 解析 tristate 值
        if value_str.lower() in ["n", "m", "y"]:
            return value_str.lower()

        # 整数或十六进制
        if re.match(r"^0x[0-9a-fA-F]+$", value_str):
            return int(value_str, 16)
        if re.match(r"^-?\d+$", value_str):
            return int(value_str)

        return value_str

    def parse_line(self, line: str) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
        """解析单行"""
        line = line.strip()

        # 跳过空行和注释
        if not line or line.startswith("#"):
            return None, None

        # 尝试匹配各种模式
        for pattern_name, pattern in self.regex_patterns.items():
            match = pattern.match(line)
            if match:
                result = {"groups": match.groups()}

                # 特殊处理
                if pattern_name == "default":
                    value_str = match.group(1)
                    result["value"] = self.parse_default_value(value_str)
                    if len(match.groups()) > 1:
                        result["condition"] = self.parse_condition(match.group(2))

                elif pattern_name in ["select", "imply"]:
                    result["target"] = match.group(1)
                    if len(match.groups()) > 1:
                        result["condition"] = self.parse_condition(match.group(2))

                elif pattern_name == "range":
                    result["min"] = int(match.group(1))
                    result["max"] = int(match.group(2))
                    if len(match.groups()) > 2:
                        result["condition"] = self.parse_condition(match.group(3))

                return pattern_name, result

        return None, None

    def parse_file(self, file_path: str, search_paths: Optional[List[str]] = None, var_map: Optional[Dict[str, str]] = None) -> List[Any]:
        import os
        logger.info("Parsing file: %s", file_path)

        if not os.path.exists(file_path):
            logger.error("File not found: %s", file_path)
            return []

        items: List[Any] = []
        in_help = False
        help_text: List[str] = []
        current_property: Optional[ConfigProperty] = None
        parent_stack: List[Any] = []  # 维护当前父级（menu/choice）
        # 条件堆栈：用于处理 if/endif 包裹条件
        cond_stack: List[Condition] = []
        # 兜底：保证 var_map / search_paths 有可用默认值
        if var_map is None:
            var_map = {}
        if search_paths is None:
            search_paths = [os.path.dirname(file_path)]

        with open(file_path, "r", encoding="utf-8") as f:
            lines = f.readlines()

        self._visited_files = getattr(self, "_visited_files", set())
        if file_path in self._visited_files:
            logger.info("Already parsed: %s, skipping", file_path)
            return items
        self._visited_files.add(file_path)

        for line in lines:
            # 处理 help 文本块
            if in_help:
                # Kconfig 规范：help 后的内容需以空格或制表符缩进；空行也属于 help
                if line.startswith(' ') or line.startswith('\t') or line.strip() == '':
                    help_text.append(line.rstrip())
                    continue
                else:
                    # 帮助文本结束，提交累积内容
                    in_help = False
                    if current_property:
                        # 统一左侧缩进，去掉首列空白
                        current_property.help = "\n".join([h.lstrip() for h in help_text]).rstrip()
                    help_text = []
                    # 不 return，当前行将按正常语法再处理

            if not in_help:
                pattern_name, result = self.parse_line(line)
                # 未识别的语句，收集并写日志（前10条详细输出）
                if pattern_name is None and result is None:
                    if line.strip() and not line.strip().startswith('#'):
                        self._unknown_lines_count += 1
                        if self._unknown_lines_count <= 10:
                            logger.warning("Unrecognized Kconfig line in %s: '%s'", file_path, line.strip())
                    continue
                # 处理 include/source 指令：递归解析被包含的文件
                if pattern_name in ("source", "rsource", "osource", "orsource"):
                    try:
                        src = result["groups"][0]
                    except Exception:
                        src = None

                    optional = pattern_name in ("osource", "orsource")

                    if src:
                        # 变量展开：优先 var_map，其次环境变量
                        mapped = None
                        if src.startswith("$"):
                            key = src[1:]
                            mapped = var_map.get(key)
                            if mapped is None:
                                mapped = os.environ.get(key)

                        # 直接以 src 名称查找（无 $ 前缀场景）
                        if mapped is None:
                            mapped = var_map.get(src)
                            if mapped is None and src in os.environ:
                                mapped = os.environ.get(src)

                        candidates = []
                        matches = []

                        def _push_candidate(p: str):
                            candidates.append(p)
                            if os.path.exists(p):
                                matches.append(p)

                        if mapped:
                            test = mapped
                            if not os.path.isabs(test):
                                test = os.path.join(os.path.dirname(file_path), test)
                            _push_candidate(test)
                        else:
                            # 相对路径尝试
                            rel = os.path.join(os.path.dirname(file_path), src)
                            _push_candidate(rel)

                            # 搜索路径尝试
                            for sp in search_paths:
                                cand = os.path.join(sp, src)
                                _push_candidate(cand)

                        if not matches:
                            # 组件目录下 Kconfig 智能查找
                            if src.lower() in ("kconfig", "kconfigfile"):
                                try:
                                    comp_root = os.path.join(os.path.dirname(file_path), "components")
                                    found = []
                                    if os.path.isdir(comp_root):
                                        for name in os.listdir(comp_root):
                                            try:
                                                p = os.path.join(comp_root, name, "Kconfig")
                                                if os.path.exists(p) and p not in found:
                                                    found.append(p)
                                            except Exception:
                                                pass
                                    if found:
                                        logger.info(
                                            "Heuristic include for %s found %d component Kconfig files in search_paths",
                                            src,
                                            len(found),
                                        )
                                        matches = found
                                    else:
                                        msg = (
                                            "Optional include not found: %s (candidates: %s) from %s"
                                            if optional
                                            else "Included source not found: %s (candidates tested: %s) from %s"
                                        )
                                        logger.log(logging.INFO if optional else logging.WARNING, msg, src, candidates, file_path)
                                except Exception:
                                    logger.log(logging.INFO if optional else logging.WARNING,
                                               ("Optional include not found: %s (candidates: %s) from %s" if optional else "Included source not found: %s (candidates tested: %s) from %s"),
                                               src, candidates, file_path)
                        else:
                            for src_path in matches:
                                logger.info(
                                    "Including Kconfig file: %s (from %s)", src_path, file_path
                                )
                                included_items = self.parse_file(src_path, search_paths=search_paths, var_map=var_map)

                                # 将被包含的项插入到当前上下文（如果有的话），否则作为顶层项
                                if parent_stack:
                                    parent = parent_stack[-1]
                                    if hasattr(parent, "children"):
                                        for inc in included_items:
                                            try:
                                                parent.children.append(inc)
                                                try:
                                                    inc.parent = parent
                                                except Exception:
                                                    pass
                                            except Exception:
                                                pass
                                else:
                                    for inc in included_items:
                                        items.append(inc)

                    continue

                if pattern_name == "help":
                    in_help = True
                    continue

                if pattern_name == "mainmenu":
                    # 顶层主菜单提示，作为根菜单入栈，使后续顶层项成为其子项
                    mm_id = self.generate_id()
                    main_menu = MenuItem(id=mm_id, name="mainmenu", prompt=result["groups"][0])
                    self.menu_items[mm_id] = main_menu
                    items.append(main_menu)
                    # 将 mainmenu 置为当前父级
                    parent_stack.append(main_menu)
                    current_property = None
                    continue

                elif pattern_name == "menu":
                    menu_id = self.generate_id()
                    menu_item = MenuItem(
                        id=menu_id,
                        name=f"menu_{len(self.menu_items) + 1}",
                        prompt=result["groups"][0],
                    )
                    self.menu_items[menu_id] = menu_item
                    
                    # attach to current parent or as top-level
                    if parent_stack:
                        parent = parent_stack[-1]
                        if hasattr(parent, 'children'):
                            parent.children.append(menu_item)
                            menu_item.parent = parent
                    else:
                        items.append(menu_item)
                    
                    # push new menu context
                    parent_stack.append(menu_item)
                    current_property = None
                
                elif pattern_name == "endmenu":
                    if parent_stack:
                        parent_stack.pop()
                    current_property = None
                
                elif pattern_name in ["config", "menuconfig"]:
                    config_id = self.generate_id()
                    config_name = result["groups"][0]
                    config_type = (
                        "menuconfig" if pattern_name == "menuconfig" else None
                    )
                    
                    config_property = ConfigProperty(type=config_type)
                    # 将 if 条件叠加到可见性条件上
                    if cond_stack:
                        cond_exprs = [c.expression for c in cond_stack if c and c.expression]
                        if cond_exprs:
                            combined = " && ".join([f"({e})" for e in cond_exprs])
                            if config_property.visible_if and config_property.visible_if.expression:
                                combined = f"({config_property.visible_if.expression}) && {combined}"
                            config_property.visible_if = Condition(expression=combined)
                    config_item = ConfigItem(id=config_id, name=config_name, properties=config_property)
                    self.config_items[config_id] = config_item
                    
                    # attach to current parent stack or as top-level
                    if parent_stack:
                        parent = parent_stack[-1]
                        if hasattr(parent, 'children'):
                            parent.children.append(config_item)
                            config_item.parent = parent
                    else:
                        items.append(config_item)
                    current_property = config_property
                
                elif pattern_name == "choice":
                    choice_id = self.generate_id()
                    prompt = result["groups"][0] if result["groups"] and result["groups"][0] else None
                    choice_item = ChoiceItem(id=choice_id, prompt=prompt)
                    # attach to parent or as top-level
                    if parent_stack:
                        parent = parent_stack[-1]
                        if hasattr(parent, 'children'):
                            parent.children.append(choice_item)
                            # 不需要设置 current_property
                    else:
                        items.append(choice_item)
                    parent_stack.append(choice_item)
                    current_property = None
                
                elif pattern_name == "endchoice":
                    if parent_stack:
                        parent_stack.pop()
                    current_property = None
                
                elif pattern_name == "comment":
                    comment_id = self.generate_id()
                    comment_text = result["groups"][0]
                    # 注释也附加 if 条件
                    cond = None
                    if cond_stack:
                        cond_exprs = [c.expression for c in cond_stack if c and c.expression]
                        if cond_exprs:
                            combined = " && ".join([f"({e})" for e in cond_exprs])
                            cond = Condition(expression=combined)
                    comment_item = CommentItem(id=comment_id, text=comment_text, condition=cond)
                    self.comment_items[comment_id] = comment_item
                    if parent_stack:
                        parent = parent_stack[-1]
                        if hasattr(parent, 'children'):
                            parent.children.append(comment_item)
                            comment_item.parent = parent
                    else:
                        items.append(comment_item)
                    current_property = None
                
                elif pattern_name == "type" and current_property:
                    t = result["groups"][0].lower()
                    current_property.type = t
                    # 如果有 if 条件，附加到可见性条件
                    if cond_stack:
                        cond_exprs = [c.expression for c in cond_stack if c and c.expression]
                        if cond_exprs:
                            combined = " && ".join([f"({e})" for e in cond_exprs])
                            if current_property.visible_if and current_property.visible_if.expression:
                                combined = f"({current_property.visible_if.expression}) && {combined}"
                            current_property.visible_if = Condition(expression=combined)
                
                elif pattern_name == "prompt" and current_property:
                    current_property.prompt = result["groups"][0]
                    # 附加 if 条件
                    if result.get("condition"):
                        current_property.visible_if = result["condition"]
                    else:
                        if cond_stack:
                            cond_exprs = [c.expression for c in cond_stack if c and c.expression]
                            if cond_exprs:
                                combined = " && ".join([f"({e})" for e in cond_exprs])
                                if current_property.visible_if and current_property.visible_if.expression:
                                    combined = f"({current_property.visible_if.expression}) && {combined}"
                                current_property.visible_if = Condition(expression=combined)
                
                elif pattern_name == "depends_on" and current_property:
                    current_property.depends_on = self.parse_condition(result["groups"][0])
                elif pattern_name == "visible_if" and current_property:
                    current_property.visible_if = self.parse_condition(result["groups"][0])
                elif pattern_name == "default" and current_property:
                    default_value = DefaultValue(value=result["value"], condition=result.get("condition"))
                    current_property.default = default_value
                elif pattern_name == "select" and current_property:
                    selection = Selection(target=result["target"], condition=result.get("condition"))
                    current_property.select.append(selection)
                elif pattern_name == "imply" and current_property:
                    implication = Selection(target=result["target"], condition=result.get("condition"))
                    current_property.imply.append(implication)
                elif pattern_name == "range" and current_property:
                    range_restriction = RangeRestriction(min=result["min"], max=result["max"], condition=result.get("condition"))
                    current_property.range = range_restriction
                elif pattern_name == "option_env":
                    # 允许在 Kconfig 中声明需要从环境读取的变量，供 include 解析使用
                    var_name = (result["groups"][0] or '').strip()
                    if var_name:
                        if var_name not in var_map:
                            var_map[var_name] = os.environ.get(var_name)
                        logger.info("Option env detected: %s=%s", var_name, var_map.get(var_name))
                elif pattern_name == "if":
                    expr = result["groups"][0]
                    cond_stack.append(Condition(expression=expr))
                elif pattern_name == "endif":
                    if cond_stack:
                        cond_stack.pop()
        logger.info("Finished parsing file: %s, found %d top-level items (unknown: %d)", file_path, len(items), self._unknown_lines_count)
        return items

    def evaluate_conditions(self, config_values: Dict[str, Any]) -> None:
        """计算所有条件表达式"""
        # 计算配置项的条件
        for config_item in self.config_items.values():
            if config_item.properties.depends_on:
                config_item.properties.depends_on.evaluated = (
                    self.expression_evaluator.evaluate(
                        config_item.properties.depends_on.expression, config_values
                    )
                )
                config_item.is_enabled = config_item.properties.depends_on.evaluated

            if config_item.properties.visible_if:
                config_item.properties.visible_if.evaluated = (
                    self.expression_evaluator.evaluate(
                        config_item.properties.visible_if.expression, config_values
                    )
                )
                config_item.is_visible = config_item.properties.visible_if.evaluated

        # 计算注释的条件
        for comment_item in self.comment_items.values():
            if comment_item.condition:
                comment_item.condition.evaluated = self.expression_evaluator.evaluate(
                    comment_item.condition.expression, config_values
                )
